// Copyright (C) 2002-2006 Aaron Griffith
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "CPLYMeshFileLoader.h"

#include <stdlib.h>
#include <iostream>

#include "SAnimatedMesh.h"
#include "SMesh.h"
#include "fast_atof.h"

#define CPLYMeshFileLoaderIsVerbose 1

namespace irr
{
	namespace scene
	{

			//! Constructor
			CPLYMeshFileLoader::CPLYMeshFileLoader(IMeshManipulator* meshm, ILogger* logm)
							   : mm(meshm), log(logm)
			{ }

			//! destructor
			CPLYMeshFileLoader::~CPLYMeshFileLoader()
			{ }

			//! returns true if the file maybe is able to be loaded by this class
			//! based on the file extension (e.g. ".cob")
			bool CPLYMeshFileLoader::isALoadableFileExtension(const c8* fileName)
			{
				core::stringc file = fileName;
				unsigned int size = file.size();
				if (file[size-3] != 'p')
					return false;
				if (file[size-2] != 'l')
					return false;
				if (file[size-1] != 'y')
					return false;
				return true;
			}

			//! creates/loads an animated mesh from the file.
			//! \return Pointer to the created mesh. Returns 0 if loading failed.
			//! If you no longer need the mesh, you should call IAnimatedMesh::drop().
			//! See IUnknown::drop() for more information.
			IAnimatedMesh* CPLYMeshFileLoader::createMesh(irr::io::IReadFile* file)
			{
				SAnimatedMesh* out_mesh = new SAnimatedMesh();
				SMesh* asm_mesh = new SMesh();
				meshbuffer = new SMeshBuffer();

				meshbuffer->Material.Wireframe = false;
				//meshbuffer->Material.Lighting = false;

				char* buf = (char*)(malloc(file->getSize()+1));
				file->read(buf, file->getSize());
				core::stringc data = buf;
				free(buf);

				core::array<core::stringc> lines = splitLines(data);

				if (parseHeader(lines))
				{
					delete out_mesh;
					delete asm_mesh;
					delete meshbuffer;
					return 0;
				}
				u32 end_index = 0;
				for (u32 k = 0; k < lines.size(); k++)
				{
					if (lines[k] == "end_header")
						end_index = k;
				}
				core::array<core::stringc> dlines;
				for (u32 k = end_index+1; k < lines.size(); k++)
				{
					if (lines[k].size() != 0)
						dlines.push_back(lines[k]);
				}

				if (parseData(dlines))
				{
					delete out_mesh;
					delete asm_mesh;
					delete meshbuffer;
					return 0;
				}

				meshbuffer->recalculateBoundingBox();
				asm_mesh->addMeshBuffer(meshbuffer);
				out_mesh->addMesh(asm_mesh);
				return out_mesh;
			}

			//! split s up into lines with no trailing newlines
			core::array<core::stringc> CPLYMeshFileLoader::splitLines(core::stringc s)
			{
				core::array<core::stringc> out;
				core::array<s32> pos;
				s32 i = 0;

				do
				{
					i = s.findNext('\n', i+1);
					if (i != -1)
						pos.push_back(i);
				} while (i != -1);

				i = 0;

				for (u32 j = 0; j < pos.size(); j++)
				{
					out.push_back(s.subString(i, (pos[j]-i)));
					i = pos[j];
				}
				out.push_back(s.subString(i, s.size()-i));

				for (u32 j = 0; j < out.size(); j++)
				{
					s32 x = out[j].findFirstChar("\n\r", 2);
					while (x == 0)
					{
						out[j] = out[j].subString(1, out[j].size()-1);
						x = out[j].findFirstChar("\n\r", 2);
					}
					if (x != -1)
						out[j] = out[j].subString(0, x);
				}

				/*for (u32 j = 0; j < out.size(); j++)
				{
					std::cout << out[j].c_str() << std::endl;
				}*/

				return out;
			}

			//! Split up a string by spaces
			core::array<core::stringc> CPLYMeshFileLoader::splitSpaces(core::stringc s)
			{
				core::array<core::stringc> out;
				core::array<s32> pos;
				s32 i = 0;

				do
				{
					i = s.findNext(' ', i+1);
					if (i != -1)
						pos.push_back(i);
				} while (i != -1);

				i = 0;

				for (u32 j = 0; j < pos.size(); j++)
				{
					out.push_back(s.subString(i, (pos[j]-i)));
					i = pos[j];
				}
				out.push_back(s.subString(i, s.size()-i));

				for (u32 j = 0; j < out.size(); j++)
				{
					s32 x = out[j].findFirstChar(" \t", 2);
					while (x == 0)
					{
						out[j] = out[j].subString(1, out[j].size()-1);
						x = out[j].findFirstChar(" \t", 2);
					}
					if (x != -1)
						out[j] = out[j].subString(0, x);
				}

				for (u32 j = 0; j < out.size(); j++)
				{
					out[j].make_lower();
				}

				return out;
			}

			//! Parse the header into the description tree
			bool CPLYMeshFileLoader::parseHeader(core::array<core::stringc> lines)
			{
				for (u32 i = 0; i < lines.size(); i++)
				{
					if (i == 0)
					{
						if (lines[i] != "ply")
						{
							error("File is not a .PLY file");
							return true;
						}
					}

					core::array<core::stringc> command = splitSpaces(lines[i]);
					if (command.size() == 0)
					{
						// nothing
					} else if (command[0] == "format") {
						if (command.size() != 3)
						{
							error("Improper format command");
							return true;
						}
						if (command[1] != "ascii")
						{
							error("File is binary (unsupported)");
							return true;
						}
						if (command[2] != "1.0")
						{
							error("Version Mismatch", true);
						}
					} else if (command[0] == "element") {
						if (command.size() != 3)
						{
							error("Improper element command");
							return true;
						}
						struct SPlyMeshElementDescription sed;
						sed.name = command[1];
						sed.count = (u32)(atod(command[2]));
						elements.push_back(sed);
					} else if (command[0] == "property") {
						if ((command.size() != 3) && (command.size() != 5))
						{
							error("Improper property command");
							return true;
						}
						if (elements.size() == 0)
						{
							error("Property command before element definition");
							return true;
						}
						if (command.size() == 3)
						{
							struct SPlyMeshPropertyDescription desc;
							desc.name = command[2];
							desc.type = command[1];
							elements[elements.size()-1].properties.push_back(desc);
						}
						if (command.size() == 5)
						{
							struct SPlyMeshPropertyDescription desc;
							desc.name = command[4];
							desc.type = command[1];
							elements[elements.size()-1].properties.push_back(desc);
						}
					}

					if (lines[i] == "end_header")
						return false;
				}
				error("File headers do not end");
				return true;
			}

			//! Parses the data
			bool CPLYMeshFileLoader::parseData(core::array<core::stringc> lines)
			{
				u32 n = 0;
				for (u32 i = 0; i < elements.size(); i++)
					n += elements[i].count;
				if (lines.size() != n)
				{
					error("Not enough data");
					return true;
				}
				u32 last_count = 0;
				for (u32 i = 0; i < elements.size(); i++)
				{
					core::array<core::stringc> localines;
					for (u32 j = last_count; j < last_count + elements[i].count; j++)
						localines.push_back(lines[j]);
					if (parseElement(i, localines))
						return true;
					last_count = elements[i].count;
				}
				return false;
			}

			//! Handles a single vertex entry
			void CPLYMeshFileLoader::handleVertex(core::array<struct SPlyMeshValue> values)
			{
				f64 x = 0;
				f64 y = 0;
				f64 z = 0;
				f64 nx = 1;
				f64 ny = 0;
				f64 nz = 0;
				f64 s = 0;
				f64 t = 0;
				f64 red = 255;
				f64 green = 255;
				f64 blue = 255;
				f64 alpha = 255;
				for (u32 i = 0; i < values.size(); i++)
				{
					if (values[i].name == "x")
					{
						x = values[i].v;
					} else if (values[i].name == "y")
					{
						y = values[i].v;
					} else if (values[i].name == "z")
					{
						z = values[i].v;
					} else if (values[i].name == "nx")
					{
						nx = values[i].v;
					} else if (values[i].name == "ny")
					{
						ny = values[i].v;
					} else if (values[i].name == "nz")
					{
						nz = values[i].v;
					} else if (values[i].name == "s")
					{
						s = values[i].v;
					} else if (values[i].name == "t")
					{
						t = values[i].v;
					} else if (values[i].name == "red")
					{
						red = values[i].v;
					} else if (values[i].name == "green")
					{
						green = values[i].v;
					} else if (values[i].name == "blue")
					{
						blue = values[i].v;
					} else if (values[i].name == "alpha")
					{
						alpha = values[i].v;
					}
				}
				meshbuffer->Vertices.push_back(video::S3DVertex(x,y,z,nx,ny,nz,
											  video::SColor((s32)(alpha), (s32)(red), (s32)(green), (s32)(blue)),
											  s,t));
				return;
			}

			//! Handles a single face entry
			void CPLYMeshFileLoader::handleFace(core::array<struct SPlyMeshValue> values)
			{
				core::array<f64> vertex_indices;
				for (u32 i = 0; i < values.size(); i++)
				{
					if (values[i].name == "vertex_indices")
					{
						vertex_indices = values[i].list;
					}
				}
				if (vertex_indices.size() == 3)
				{
					meshbuffer->Indices.push_back((u16)(vertex_indices[0]));
					meshbuffer->Indices.push_back((u16)(vertex_indices[1]));
					meshbuffer->Indices.push_back((u16)(vertex_indices[2]));
				}
				if (vertex_indices.size() == 4)
				{
					meshbuffer->Indices.push_back((u16)(vertex_indices[0]));
					meshbuffer->Indices.push_back((u16)(vertex_indices[1]));
					meshbuffer->Indices.push_back((u16)(vertex_indices[2]));

					meshbuffer->Indices.push_back((u16)(vertex_indices[2]));
					meshbuffer->Indices.push_back((u16)(vertex_indices[3]));
					meshbuffer->Indices.push_back((u16)(vertex_indices[0]));
				}
				return;
			}

			//! Parses an individual set of elements
			bool CPLYMeshFileLoader::parseElement(u32 i, core::array<core::stringc> lines)
			{
				core::array<core::stringc> keys;
				for (u32 j = 0; j < elements[i].properties.size(); j++)
					keys.push_back(elements[i].properties[j].name);
				for (u32 j = 0; j < lines.size(); j++)
				{
					core::array<core::stringc> valuesc;
					core::array<struct SPlyMeshValue> values;
					valuesc = splitSpaces(lines[j]);
					for (u32 k = 0; k < valuesc.size(); k++)
					{
						if (valuesc[k] != "")
						{
							struct SPlyMeshValue val;
							if (elements[i].properties[k].type != "list") {
								val.isAList = false;
								val.v = atod(valuesc[k]);
							} else {
								val.isAList = true;
								for (u32 x = k+1; x < k+atod(valuesc[k])+1; x++)
									val.list.push_back(atod(valuesc[x]));
								core::array<core::stringc> newvalues;
								for (u32 x = 0; x < k+1; x++)
									newvalues.push_back(valuesc[x]);
								for (u32 x = (u32)(k+atod(valuesc[k])+1); x < valuesc.size(); x++)
									newvalues.push_back(valuesc[x]);
								valuesc = newvalues;
							}
							values.push_back(val);
						}
					}
					if (values.size() != keys.size())
					{
						error("Invalid data entry");
						return true;
					}
					for (u32 x = 0; x < elements[i].properties.size(); x++)
					{
						values[x].name = elements[i].properties[x].name;
					}

					if (elements[i].name == "vertex")
						handleVertex(values);
					if (elements[i].name == "face")
						handleFace(values);

					/*for (int x = 0; x < values.size(); x++)
					{
						if (!values[x].isAList)
						{
							std::cout << values[x].name.c_str() << " : " << values[x].v << std::endl;
						}
						if (values[x].isAList)
						{
							std::cout << "LIST ";
							for (int y = 0; y < values[x].list.size(); y++)
								std::cout << values[x].list[y] << " ";
							std::cout << std::endl;
						}
					}*/
				}
				return false;
			}

			//! ascii to double
			f64 CPLYMeshFileLoader::atod(core::stringc s)
			{
				return (f64)(core::fast_atof(s.c_str()));
			}

			//! a simple error function
			void CPLYMeshFileLoader::error(core::stringc st, bool warn)
			{
				#ifdef CPLYMeshFileLoaderIsVerbose

				core::stringc s = "ERROR: .PLY Loader: ";
				if (warn)
					s = "WARNING: .PLY Loader: ";
				s += st;

				if (warn)
				{
					log->log(s.c_str(), ELL_WARNING);
				} else {
					log->log(s.c_str(), ELL_ERROR);
				}

				#endif // CPLYMeshFileLoaderIsVerbose
			}

	} // end namespace scene
} // end namespace irr
